  "use strict"


  import {IComponent} from "./interfaces/IComponent";
  import {GroupObserverException} from "./exceptions/GroupObserverException";
  import {GroupEventType} from "./Matcher";
  import {Group} from "./Group";
  import {Entity} from "./Entity";

  export class GroupObserver {

    /**
     * Entities being observed
     * @type {Object<string,entitas.Entity>}
     * @name entitas.GroupObserver#collectedEntities */
    public get collectedEntities() {
      return this._collectedEntities;
    }

    private _collectedEntities = {}
    protected _groups: Array<Group<Entity>> = null
    protected _eventTypes: Array<GroupEventType> = null
    protected _addEntityCache: Group.GroupChanged = null


    /**
     * @constructor
     * @param {Array<entitas.Group>} groups
     * @param {number} eventTypes
     */
    constructor(groups, eventTypes) {
      this._groups = Array.isArray(groups) ? groups : [groups]
      this._eventTypes = Array.isArray(eventTypes) ? eventTypes : [eventTypes]

      if (groups.length !== eventTypes.length) {
        throw new GroupObserverException("Unbalanced count with groups (" + groups.length +
            ") and event types (" + eventTypes.length + ")")
      }
      this._collectedEntities = {}
      this._addEntityCache = this.addEntity
      this.activate()
    }

    /**
     * Activate events
     */
    activate() {
      for (let i = 0, groupsLength = this._groups.length; i < groupsLength; i++) {
        const group: Group<Entity> = this._groups[i]
        const eventType: GroupEventType = this._eventTypes[i]

        if (eventType === GroupEventType.OnEntityAdded) {
          // @ts-ignore
          group.onEntityAdded.remove(this._addEntityCache)
          // @ts-ignore
          group.onEntityAdded.add(this._addEntityCache)
        } else if (eventType === GroupEventType.OnEntityRemoved) {
          // @ts-ignore
          group.onEntityRemoved.remove(this._addEntityCache)
          // @ts-ignore
          group.onEntityRemoved.add(this._addEntityCache)
        } else if (eventType === GroupEventType.OnEntityAddedOrRemoved) {
          // @ts-ignore
          group.onEntityAdded.remove(this._addEntityCache)
          // @ts-ignore
          group.onEntityAdded.add(this._addEntityCache)
          // @ts-ignore
          group.onEntityRemoved.remove(this._addEntityCache)
          // @ts-ignore
          group.onEntityRemoved.add(this._addEntityCache)
        } else {
          throw `Invalid eventType [${typeof eventType}:${eventType}] in GroupObserver::activate`
        }
      }
    }

    /**
     * Deavtivate events
     */
    deactivate() {
      for (let i = 0, groupsLength = this._groups.length; i < groupsLength; i++) {
        const group = this._groups[i]

        group.onEntityAdded.remove(this._addEntityCache)
        group.onEntityRemoved.remove(this._addEntityCache)

        this.clearCollectedEntities()
      }

    }

    /**
     * Clear the list of entities
     */
    clearCollectedEntities() {
      for (let e in this._collectedEntities) {
        this._collectedEntities[e].release()
      }
      this._collectedEntities = {}
    }

    /**
     * Adds an entity to this observer group
     * @param group
     * @param {entitas.Entity}entity
     * @param index
     * @param {entitas.IComponent}component
     */
    addEntity = (group, entity: Entity, index: number, component: IComponent) => {
      if (!(entity.id in this._collectedEntities)) {
        this._collectedEntities[entity.id] = entity
        entity.addRef()
      }
    }
  }
